Micron Document
๐ŸŽ–๏ธGitะฏั€ะฐ๐ŸŽ–๏ธ

Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonTest / kotlin / org / meshtastic / feature / firmware / ota / dfu / SecureDfuTransportTest.kt

Displaying Raw โ€ข Download

feature/firmware/src/commonTest/kotlin/org/meshtastic/feature/firmware/ota/dfu/SecureDfuTransportTest.kt 9fa9f1e341ee8d2c8fd9fabf9253b2f012fc8c95 (9fa9f1e3) Text, 33.28 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4)

Tff7b72package T7ee787org.meshtastic.feature.firmware.ota.dfu

Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787org.meshtastic.core.ble.BleCharacteristic
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnection
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.ble.BleConnectionState
Tff7b72import T7ee787org.meshtastic.core.ble.BleDevice
Tff7b72import T7ee787org.meshtastic.core.ble.BleService
Tff7b72import T7ee787org.meshtastic.core.ble.BleWriteType
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleConnection
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleConnectionFactory
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleDevice
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleScanner
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleService
Tff7b72import T7ee787org.meshtastic.core.testing.FakeBleWrite
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertContentEquals
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertIs
Tff7b72import T7ee787kotlin.test.assertTrue
Tff7b72import T7ee787kotlin.time.Duration

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tff7b72class T56d364SecureDfuTransportTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff00:11:22:33:44:55Ta5d6ff"
Tff7b72private Tff7b72val Te6edf3dfuAddress Tff7b72= Ta5d6ff"Ta5d6ff00:11:22:33:44:56Ta5d6ff"

T8b949e// -----------------------------------------------------------------------
T8b949e// Phase 1: Buttonless DFU trigger
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`triggerButtonlessDfu writes reboot opcode through BleService`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3connection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3transport Tff7b72=
Te6edf3SecureDfuTransportTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3FakeBleConnectionFactoryTb4b4b4(Te6edf3connectionTb4b4b4)Tb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Te6edf3dispatcher Tff7b72= Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4,
Tb4b4b4)

Te6edf3scannerTb4b4b4.Te6edf3emitDeviceTb4b4b4(Te6edf3FakeBleDeviceTb4b4b4(Te6edf3addressTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3transportTb4b4b4.Te6edf3triggerButtonlessDfuTb4b4b4(Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4)
T8b949e// Find the buttonless write (ignore any observation-triggered writes)
Tff7b72val Te6edf3buttonlessWrites Tff7b72=
Te6edf3connectionTb4b4b4.Te6edf3serviceTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3BUTTONLESS_NO_BONDS Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3buttonlessWritesTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffShould have exactly one buttonless DFU writeTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3write Tff7b72= Te6edf3buttonlessWritesTb4b4b4.Te6edf3singleTb4b4b4(Tb4b4b4)
Te6edf3assertContentEqualsTb4b4b4(Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x01Tb4b4b4)Tb4b4b4, Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4, Te6edf3writeTb4b4b4.Te6edf3writeTypeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3connectionTb4b4b4.Te6edf3disconnectCallsTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`triggerButtonlessDfu falls back to legacy DFU service when secure FE59 is missing`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3connection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3apply Tb4b4b4{ Te6edf3missingServices Tff7b72+Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3SERVICE Tb4b4b4}
Tff7b72val Te6edf3transport Tff7b72=
Te6edf3SecureDfuTransportTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3FakeBleConnectionFactoryTb4b4b4(Te6edf3connectionTb4b4b4)Tb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Te6edf3dispatcher Tff7b72= Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4,
Tb4b4b4)

Te6edf3scannerTb4b4b4.Te6edf3emitDeviceTb4b4b4(Te6edf3FakeBleDeviceTb4b4b4(Te6edf3addressTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3transportTb4b4b4.Te6edf3triggerButtonlessDfuTb4b4b4(Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6ffLegacy fallback should succeed when FE59 is absentTa5d6ff"Tb4b4b4)
T8b949e// No write should have hit the secure characteristic.
Te6edf3assertTrueTb4b4b4(
Te6edf3connectionTb4b4b4.Te6edf3serviceTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3BUTTONLESS_NO_BONDS Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffShould not write to secure buttonless characteristic when FE59 is missingTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// Exactly one write of 0x01 (START_DFU) should have hit the legacy control point.
Tff7b72val Te6edf3legacyWrites Tff7b72= Te6edf3connectionTb4b4b4.Te6edf3serviceTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3LegacyDfuUuidsTb4b4b4.Te6edf3CONTROL_POINT Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3legacyWritesTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffShould have exactly one legacy DFU trigger writeTa5d6ff"Tb4b4b4)
Te6edf3assertContentEqualsTb4b4b4(Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x01Tb4b4b4, T79c0ff0Te6edf3x04Tb4b4b4)Tb4b4b4, Te6edf3legacyWritesTb4b4b4.Te6edf3singleTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3dataTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4, Te6edf3legacyWritesTb4b4b4.Te6edf3singleTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3writeTypeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3connectionTb4b4b4.Te6edf3disconnectCallsTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`connectToDfuMode succeeds using shared BleService observation`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3connection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3transport Tff7b72=
Te6edf3SecureDfuTransportTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3FakeBleConnectionFactoryTb4b4b4(Te6edf3connectionTb4b4b4)Tb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Te6edf3dispatcher Tff7b72= Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4,
Tb4b4b4)

Te6edf3scannerTb4b4b4.Te6edf3emitDeviceTb4b4b4(Te6edf3FakeBleDeviceTb4b4b4(Te6edf3dfuAddressTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3transportTb4b4b4.Te6edf3connectToDfuModeTb4b4b4(Tb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Abort & close
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`abort writes ABORT opcode through BleService`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3connection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3transport Tff7b72=
Te6edf3SecureDfuTransportTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3FakeBleConnectionFactoryTb4b4b4(Te6edf3connectionTb4b4b4)Tb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Te6edf3dispatcher Tff7b72= Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4,
Tb4b4b4)

Te6edf3transportTb4b4b4.Te6edf3abortTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3write Tff7b72= Te6edf3connectionTb4b4b4.Te6edf3serviceTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3singleTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4, Te6edf3writeTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuidTb4b4b4)
Te6edf3assertContentEqualsTb4b4b4(Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3ABORTTb4b4b4)Tb4b4b4, Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITH_RESPONSETb4b4b4, Te6edf3writeTb4b4b4.Te6edf3writeTypeTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Phase 3: Init packet transfer
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`transferInitPacket sends PRN 0 not 10`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff2T79c0ff8Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Find the SET_PRN write
Tff7b72val Te6edf3prnWrite Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointWritesTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTff7b72[T79c0ff0Tff7b72] Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRN Tb4b4b4}

T8b949e// PRN value is bytes [1..2] as little-endian 16-bit integer
Tff7b72val Te6edf3prnValue Tff7b72= Tb4b4b4(Te6edf3prnWriteTb4b4b4.Te6edf3dataTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3or Tb4b4b4(Tb4b4b4(Te6edf3prnWriteTb4b4b4.Te6edf3dataTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3prnValueTb4b4b4, Ta5d6ff"Ta5d6ffInit packet PRN should be 0, not Tffd700$Te6edf3prnValueTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`transferFirmware sends PRN 10`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3firmwareCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3firmwareCrcTb4b4b4, Te6edf3firmwareData Tff7b72= Te6edf3firmwareTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3progressValues Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657FloatTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{ Te6edf3progressValuesTb4b4b4.Te6edf3addTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferFirmware failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Find the SET_PRN write
Tff7b72val Te6edf3prnWrite Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointWritesTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTff7b72[T79c0ff0Tff7b72] Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRN Tb4b4b4}

Tff7b72val Te6edf3prnValue Tff7b72= Tb4b4b4(Te6edf3prnWriteTb4b4b4.Te6edf3dataTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3or Tb4b4b4(Tb4b4b4(Te6edf3prnWriteTb4b4b4.Te6edf3dataTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4, Te6edf3prnValueTb4b4b4, Ta5d6ff"Ta5d6ffFirmware PRN should be 10Ta5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`transferFirmware reports progress`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3firmwareCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3firmwareCrcTb4b4b4, Te6edf3firmwareData Tff7b72= Te6edf3firmwareTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3progressValues Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657FloatTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{ Te6edf3progressValuesTb4b4b4.Te6edf3addTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferFirmware failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3progressValuesTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffShould report at least one progress valueTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1.0fTb4b4b4, Te6edf3progressValuesTb4b4b4.Te6edf3lastTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffFinal progress should be 1.0Ta5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Resume logic
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`resume - device has complete data - just execute`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff2T79c0ff8Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)

T8b949e// SELECT returns: device already has all bytes with matching CRC
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(
Te6edf3DfuResponderTb4b4b4(
Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4,
Te6edf3selectOffset Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3selectCrc Tff7b72= Te6edf3initCrcTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Should NOT have sent any CREATE command โ€” only SET_PRN, SELECT, and EXECUTE
Tff7b72val Te6edf3opcodes Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointOpcodesTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tff7b72!in Te6edf3opcodesTb4b4b4,
Ta5d6ff"Ta5d6ffShould not send CREATE when device already has complete data. Opcodes: Tffd700${Te6edf3opcodesTb4b4b4.Te6edf3hexListTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTE Tff7b72in Te6edf3opcodesTb4b4b4, Ta5d6ff"Ta5d6ffShould send EXECUTE for complete dataTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`resume - CRC mismatch - restart from offset 0`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff2T79c0ff8Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)

T8b949e// SELECT returns: device has bytes but CRC is wrong
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(
Te6edf3DfuResponderTb4b4b4(
Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4,
Te6edf3selectOffset Tff7b72= T79c0ff6T79c0ff4Tb4b4b4,
Te6edf3selectCrc Tff7b72= T79c0ff0Te6edf3xDEADBEEFTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4, T8b949e// Wrong CRC
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Should have sent CREATE (restarting from 0)
Tff7b72val Te6edf3opcodes Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointOpcodesTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tff7b72in Te6edf3opcodesTb4b4b4, Ta5d6ff"Ta5d6ffShould send CREATE when CRC mismatches (restart from 0)Ta5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`resume - object boundary - execute last then continue`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

T8b949e// Firmware with 2 objects worth of data (maxObjectSize=4096)
Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff8T79c0ff1T79c0ff9T79c0ff2Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3firmwareCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4)
Tff7b72val Te6edf3firstObjectCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4, Te6edf3length Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4)

T8b949e// SELECT returns: device is at object boundary (4096 bytes, exactly 1 full object)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(
Te6edf3DfuResponderTb4b4b4(
Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3totalCrc Tff7b72= Te6edf3firmwareCrcTb4b4b4,
Te6edf3selectOffset Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4,
Te6edf3selectCrc Tff7b72= Te6edf3firstObjectCrcTb4b4b4,
Te6edf3maxObjectSize Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4,
Te6edf3firmwareData Tff7b72= Te6edf3firmwareTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3progressValues Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657FloatTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{ Te6edf3progressValuesTb4b4b4.Te6edf3addTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferFirmware failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Should have sent EXECUTE first (for the resumed first object), then CREATE (for the second)
Tff7b72val Te6edf3opcodes Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointOpcodesTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTE Tff7b72in Te6edf3opcodesTb4b4b4, Ta5d6ff"Ta5d6ffShould send EXECUTE for first objectTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tff7b72in Te6edf3opcodesTb4b4b4, Ta5d6ff"Ta5d6ffShould send CREATE for second objectTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Execute retry on INVALID_OBJECT
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`execute retry on INVALID_OBJECT for final object`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3firmwareCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4)

Tff7b72var Te6edf3executeCount Tff7b72= T79c0ff0
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(
Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3firmwareCrcTb4b4b4, Te6edf3firmwareData Tff7b72= Te6edf3firmwareTb4b4b4) Tb4b4b4{ Te6edf3opcode Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4) Tb4b4b4{
Te6edf3executeCountTff7b72+Tff7b72+
Tff7b72if Tb4b4b4(Te6edf3executeCount Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Tb4b4b4{
T8b949e// First EXECUTE returns INVALID_OBJECT
Te6edf3buildDfuFailureTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4, Te6edf3DfuResultCodeTb4b4b4.Te6edf3INVALID_OBJECTTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3buildDfuSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null T8b949e// Default handling
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertTrueTb4b4b4(
Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4,
Ta5d6ff"Ta5d6fftransferFirmware should succeed after INVALID_OBJECT retry: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3executeCountTb4b4b4, Ta5d6ff"Ta5d6ffShould have tried EXECUTE twiceTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Checksum validation
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`transferFirmware fails on CRC mismatch after object`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

T8b949e// Use exactly 200 bytes: with default MTU=20 that's 10 packets.
T8b949e// PRN=10 fires at packet 10 but pos==until so the PRN wait is skipped,
T8b949e// and the explicit CALCULATE_CHECKSUM will get the wrong CRC.
Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}

T8b949e// Use a wrong CRC so the checksum after transfer won't match.
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= T79c0ff0Te6edf3xDEADBEEFTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isFailureTb4b4b4, Ta5d6ff"Ta5d6ffShould fail on CRC mismatchTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3exception Tff7b72= Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)
Te6edf3assertIsTff7b72<Te6edf3DfuExceptionTb4b4b4.Te6edf3ChecksumMismatchTff7b72>Tb4b4b4(Te6edf3exceptionTb4b4b4, Ta5d6ff"Ta5d6ffShould throw ChecksumMismatch, got: Tffd700$Te6edf3exceptionTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Packet writing: MTU and write type
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`transferInitPacket writes packet data WITHOUT_RESPONSE to PACKET characteristic`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff6T79c0ff4Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Check PACKET writes
Tff7b72val Te6edf3packetWrites Tff7b72= Te6edf3envTb4b4b4.Te6edf3packetWritesTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3packetWritesTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffShould have written packet dataTa5d6ff"Tb4b4b4)
Te6edf3packetWritesTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3write Tff7b72-Tff7b72>
Te6edf3assertEqualsTb4b4b4(Te6edf3BleWriteTypeTb4b4b4.Te6edf3WITHOUT_RESPONSETb4b4b4, Te6edf3writeTb4b4b4.Te6edf3writeTypeTb4b4b4, Ta5d6ff"Ta5d6ffPacket data should use WITHOUT_RESPONSETa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// Reconstruct the written data
Tff7b72val Te6edf3writtenData Tff7b72= Te6edf3packetWritesTb4b4b4.Te6edf3flatMap Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3toListTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Te6edf3assertContentEqualsTb4b4b4(Te6edf3initPacketTb4b4b4, Te6edf3writtenDataTb4b4b4, Ta5d6ff"Ta5d6ffWritten packet data should match init packetTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`packet writes respect MTU size`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Te6edf3mtu Tff7b72= T79c0ff6T79c0ff4Tb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

Tff7b72val Te6edf3packetWrites Tff7b72= Te6edf3envTb4b4b4.Te6edf3packetWritesTb4b4b4(Tb4b4b4)
Te6edf3packetWritesTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3write Tff7b72-Tff7b72>
Te6edf3assertTrueTb4b4b4(Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3size Tff7b72<Tff7b72= T79c0ff6T79c0ff4Tb4b4b4, Ta5d6ff"Ta5d6ffPacket write size Tffd700${Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff exceeds MTU of 64Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3writtenData Tff7b72= Te6edf3packetWritesTb4b4b4.Te6edf3flatMap Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3toListTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Te6edf3assertContentEqualsTb4b4b4(Te6edf3initPacketTb4b4b4, Te6edf3writtenDataTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`default MTU is 20 bytes when connection returns null`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Te6edf3mtu Tff7b72= Tff7b72nullTb4b4b4)

Tff7b72val Te6edf3initPacket Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff6T79c0ff4Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3initCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3initPacketTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(Te6edf3DfuResponderTb4b4b4(Te6edf3totalSize Tff7b72= Te6edf3initPacketTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3totalCrc Tff7b72= Te6edf3initCrcTb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferInitPacketTb4b4b4(Te6edf3initPacketTb4b4b4)

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferInitPacket failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

Tff7b72val Te6edf3packetWrites Tff7b72= Te6edf3envTb4b4b4.Te6edf3packetWritesTb4b4b4(Tb4b4b4)
Te6edf3packetWritesTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3write Tff7b72-Tff7b72>
Te6edf3assertTrueTb4b4b4(
Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3size Tff7b72<Tff7b72= T79c0ff2T79c0ff0Tb4b4b4,
Ta5d6ff"Ta5d6ffPacket write size Tffd700${Te6edf3writeTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff should not exceed default MTU of 20Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Multi-object firmware transfer
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`transferFirmware splits data into objects of maxObjectSize`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3env Tff7b72= Te6edf3createConnectedTransportTb4b4b4(Tb4b4b4)

T8b949e// 6000 bytes with maxObjectSize=4096 โ†’ 2 objects (4096 + 1904)
Tff7b72val Te6edf3firmware Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff6T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3firmwareCrc Tff7b72= Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Te6edf3firmwareTb4b4b4)
Te6edf3envTb4b4b4.Te6edf3configureResponderTb4b4b4(
Te6edf3DfuResponderTb4b4b4(
Te6edf3totalSize Tff7b72= Te6edf3firmwareTb4b4b4.Te6edf3sizeTb4b4b4,
Te6edf3totalCrc Tff7b72= Te6edf3firmwareCrcTb4b4b4,
Te6edf3maxObjectSize Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4,
Te6edf3firmwareData Tff7b72= Te6edf3firmwareTb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3progressValues Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657FloatTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3result Tff7b72= Te6edf3envTb4b4b4.Te6edf3transportTb4b4b4.Te6edf3transferFirmwareTb4b4b4(Te6edf3firmwareTb4b4b4) Tb4b4b4{ Te6edf3progressValuesTb4b4b4.Te6edf3addTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3resultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6fftransferFirmware failed: Tffd700${Te6edf3resultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Should have 2 CREATE commands
Tff7b72val Te6edf3createWrites Tff7b72= Te6edf3envTb4b4b4.Te6edf3controlPointWritesTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTff7b72[T79c0ff0Tff7b72] Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tb4b4b4}
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3createWritesTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffShould send 2 CREATE commands for 6000 bytes / 4096 maxTa5d6ff"Tb4b4b4)

T8b949e// First CREATE should request 4096 bytes, second should request 1904
Tff7b72val Te6edf3firstSize Tff7b72= Te6edf3createWritesTff7b72[T79c0ff0Tff7b72]Tb4b4b4.Te6edf3dataTb4b4b4.Te6edf3dropTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3readIntLeTb4b4b4(T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3secondSize Tff7b72= Te6edf3createWritesTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3dataTb4b4b4.Te6edf3dropTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3readIntLeTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff4T79c0ff0T79c0ff9T79c0ff6Tb4b4b4, Te6edf3firstSizeTb4b4b4, Ta5d6ff"Ta5d6ffFirst object size should be 4096Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1T79c0ff9T79c0ff0T79c0ff4Tb4b4b4, Te6edf3secondSizeTb4b4b4, Ta5d6ff"Ta5d6ffSecond object size should be 1904Ta5d6ff"Tb4b4b4)

T8b949e// Progress should end at 1.0
Te6edf3assertEqualsTb4b4b4(T79c0ff1.0fTb4b4b4, Te6edf3progressValuesTb4b4b4.Te6edf3lastTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff2Tb4b4b4, Te6edf3progressValuesTb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffShould have 2 progress reports (one per object)Ta5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Test infrastructure
T8b949e// -----------------------------------------------------------------------

T8b949e/** A test environment holding a connected transport and its backing fakes. */
Tff7b72private Tff7b72class T56d364TestEnvTb4b4b4(Tff7b72val Te6edf3transportTb4b4b4: Te6edf3SecureDfuTransportTb4b4b4, Tff7b72val Te6edf3serviceTb4b4b4: Te6edf3AutoRespondingBleServiceTb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8ffconfigureResponderTb4b4b4(Te6edf3responderTb4b4b4: Te6edf3DfuResponderTb4b4b4) Tb4b4b4{
Te6edf3serviceTb4b4b4.Te6edf3responder Tff7b72= Te6edf3responder
Te6edf3serviceTb4b4b4.Te6edf3firmwareData Tff7b72= Te6edf3responderTb4b4b4.Te6edf3firmwareData
Tb4b4b4}

Tff7b72fun Td2a8ffcontrolPointWritesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3FakeBleWriteTff7b72> Tff7b72=
Te6edf3serviceTb4b4b4.Te6edf3delegateTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINT Tb4b4b4}

Tff7b72fun Td2a8ffcontrolPointOpcodesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Tffa657ByteTff7b72> Tff7b72= Te6edf3controlPointWritesTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTff7b72[T79c0ff0Tff7b72] Tb4b4b4}

Tff7b72fun Td2a8ffpacketWritesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3FakeBleWriteTff7b72> Tff7b72=
Te6edf3serviceTb4b4b4.Te6edf3delegateTb4b4b4.Te6edf3writesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3PACKET Tb4b4b4}
Tb4b4b4}

T8b949e/**
* A [BleService] wrapper that delegates to [FakeBleService] but intercepts writes to CONTROL_POINT and immediately
* emits a DFU notification response. This solves the coroutine ordering problem where `sendCommand()` writes then
* suspends on `notificationChannel.receive()` โ€” the response must be in the channel before the receive.
*
* Because [FakeBleConnection.profile] runs with [kotlinx.coroutines.Dispatchers.Unconfined], the notification
* emitted here propagates immediately through the observation flow into the transport's `notificationChannel`.
*/
Tff7b72private Tff7b72class T56d364AutoRespondingBleServiceTb4b4b4(Tff7b72val Te6edf3delegateTb4b4b4: Te6edf3FakeBleServiceTb4b4b4) Tb4b4b4: Te6edf3BleService Tb4b4b4{
Tff7b72var Te6edf3responderTb4b4b4: Te6edf3DfuResponder? Tff7b72= Tff7b72null

T8b949e/**
* The cumulative firmware offset the simulated device is at. This must match the absolute position the
* transport expects from CALCULATE_CHECKSUM responses.
*
* Updated by:
* - SELECT: set to the responder's [DfuResponder.selectOffset] (initial state)
* - CREATE: reset to [executedOffset] (device discards partial object data)
* - PACKET writes: incremented by write size
* - EXECUTE: [executedOffset] advances to current value (object committed)
*/
Tff7b72private Tff7b72var Te6edf3accumulatedPacketBytes Tff7b72= T79c0ff0

T8b949e/** The offset of the last executed (committed) object boundary. */
Tff7b72private Tff7b72var Te6edf3executedOffset Tff7b72= T79c0ff0

T8b949e/** Tracks packets since last PRN response for flow control simulation. */
Tff7b72private Tff7b72var Te6edf3packetsSincePrn Tff7b72= T79c0ff0

T8b949e/** Current PRN interval โ€” set when SET_PRN is received. 0 = disabled. */
Tff7b72private Tff7b72var Te6edf3prnInterval Tff7b72= T79c0ff0

T8b949e/** Current object size target from the last CREATE command. */
Tff7b72private Tff7b72var Te6edf3currentObjectSize Tff7b72= T79c0ff0

T8b949e/** Bytes written in the current object (resets on CREATE). */
Tff7b72private Tff7b72var Te6edf3currentObjectBytesWritten Tff7b72= T79c0ff0

T8b949e/** The firmware data being transferred, for computing partial CRCs in PRN responses. */
Tff7b72var Te6edf3firmwareDataTb4b4b4: Te6edf3ByteArray? Tff7b72= Tff7b72null

Tff7b72override Tff7b72fun Td2a8ffhasCharacteristicTb4b4b4(Te6edf3characteristicTb4b4b4: Te6edf3BleCharacteristicTb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3hasCharacteristicTb4b4b4(Te6edf3characteristicTb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffobserveTb4b4b4(Te6edf3characteristicTb4b4b4: Te6edf3BleCharacteristicTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ByteArrayTff7b72> Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3observeTb4b4b4(Te6edf3characteristicTb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffreadTb4b4b4(Te6edf3characteristicTb4b4b4: Te6edf3BleCharacteristicTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3readTb4b4b4(Te6edf3characteristicTb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffpreferredWriteTypeTb4b4b4(Te6edf3characteristicTb4b4b4: Te6edf3BleCharacteristicTb4b4b4)Tb4b4b4: Te6edf3BleWriteType Tff7b72=
Te6edf3delegateTb4b4b4.Te6edf3preferredWriteTypeTb4b4b4(Te6edf3characteristicTb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffwriteTb4b4b4(Te6edf3characteristicTb4b4b4: Te6edf3BleCharacteristicTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ByteArrayTb4b4b4, Te6edf3writeTypeTb4b4b4: Te6edf3BleWriteTypeTb4b4b4) Tb4b4b4{
Te6edf3delegateTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3characteristicTb4b4b4, Te6edf3dataTb4b4b4, Te6edf3writeTypeTb4b4b4)

Tff7b72if Tb4b4b4(Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3PACKETTb4b4b4) Tb4b4b4{
Te6edf3accumulatedPacketBytes Tff7b72+Tff7b72= Te6edf3dataTb4b4b4.Te6edf3size
Te6edf3currentObjectBytesWritten Tff7b72+Tff7b72= Te6edf3dataTb4b4b4.Te6edf3size
Te6edf3packetsSincePrnTff7b72+Tff7b72+

T8b949e// Simulate device-side PRN flow control: emit a ChecksumResult notification
T8b949e// every prnInterval packets, just like a real BLE DFU target would.
T8b949e// Skip if this is the last packet in the current object (pos == until),
T8b949e// matching the transport's `pos < until` guard.
Tff7b72val Te6edf3objectComplete Tff7b72= Te6edf3currentObjectBytesWritten Tff7b72>Tff7b72= Te6edf3currentObjectSize
Tff7b72if Tb4b4b4(Te6edf3prnInterval Tff7b72> T79c0ff0 Tff7b72&Tff7b72& Te6edf3packetsSincePrn Tff7b72>Tff7b72= Te6edf3prnInterval Tff7b72&Tff7b72& Tff7b72!Te6edf3objectCompleteTb4b4b4) Tb4b4b4{
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tff7b72val Te6edf3crc Tff7b72=
Te6edf3firmwareDataTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Tffa657itTb4b4b4, Te6edf3length Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3accumulatedPacketBytesTb4b4b4, Tffa657itTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72?: T79c0ff0
Te6edf3delegateTb4b4b4.Te6edf3emitNotificationTb4b4b4(
Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4,
Te6edf3buildChecksumResponseTb4b4b4(Te6edf3accumulatedPacketBytesTb4b4b4, Te6edf3crcTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72return
Tb4b4b4}

Tff7b72if Tb4b4b4(Te6edf3characteristicTb4b4b4.Te6edf3uuid Tff7b72=Tff7b72= Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINT Tff7b72&Tff7b72& Te6edf3dataTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3opcode Tff7b72= Te6edf3dataTff7b72[T79c0ff0Tff7b72]

T8b949e// Capture the PRN interval from SET_PRN commands
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRN Tff7b72&Tff7b72& Te6edf3dataTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff3Tb4b4b4) Tb4b4b4{
Te6edf3prnInterval Tff7b72= Tb4b4b4(Te6edf3dataTff7b72[T79c0ff1Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3or Tb4b4b4(Tb4b4b4(Te6edf3dataTff7b72[T79c0ff2Tff7b72]Tb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4) Te6edf3and T79c0ff0Te6edf3xFFTb4b4b4) Te6edf3shl T79c0ff8Tb4b4b4)
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tb4b4b4}

T8b949e// On SELECT, initialize the device's offset to the responder's selectOffset.
T8b949e// On a real device, SELECT returns the cumulative state (all executed objects +
T8b949e// any partial current object). We do NOT set executedOffset here โ€” that only
T8b949e// advances on EXECUTE, because selectOffset may include non-executed partial
T8b949e// data that the device will discard on CREATE.
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3SELECTTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3resp Tff7b72= Te6edf3responder
Tff7b72if Tb4b4b4(Te6edf3resp Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3accumulatedPacketBytes Tff7b72= Te6edf3respTb4b4b4.Te6edf3selectOffset
Te6edf3currentObjectBytesWritten Tff7b72= T79c0ff0
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tb4b4b4}
Tb4b4b4}

T8b949e// On CREATE, the device discards any partial (non-executed) data and starts a
T8b949e// fresh object. Reset accumulatedPacketBytes to the last executed boundary.
T8b949e// This correctly handles:
T8b949e// - Fresh transfer: executedOffset=0 โ†’ accumulatedPacketBytes resets to 0
T8b949e// - CRC mismatch restart: executedOffset=0 โ†’ resets to 0 (discards bad data)
T8b949e// - Multi-object: executedOffset=4096 โ†’ resets to 4096 (keeps executed data)
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tff7b72&Tff7b72& Te6edf3dataTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff6Tb4b4b4) Tb4b4b4{
Te6edf3accumulatedPacketBytes Tff7b72= Te6edf3executedOffset
Te6edf3currentObjectSize Tff7b72= Te6edf3dataTb4b4b4.Te6edf3dropTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3readIntLeTb4b4b4(T79c0ff0Tb4b4b4)
Te6edf3currentObjectBytesWritten Tff7b72= T79c0ff0
Te6edf3packetsSincePrn Tff7b72= T79c0ff0
Tb4b4b4}

T8b949e// On EXECUTE, the device commits the current object. Advance executedOffset
T8b949e// to the current accumulated position.
Tff7b72if Tb4b4b4(Te6edf3opcode Tff7b72=Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4) Tb4b4b4{
Te6edf3executedOffset Tff7b72= Te6edf3accumulatedPacketBytes
Tb4b4b4}

Tff7b72val Te6edf3resp Tff7b72= Te6edf3responder Tff7b72?: Tff7b72return
Tff7b72val Te6edf3response Tff7b72= Te6edf3respTb4b4b4.Te6edf3respondTb4b4b4(Te6edf3opcodeTb4b4b4, Te6edf3accumulatedPacketBytesTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3response Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3delegateTb4b4b4.Te6edf3emitNotificationTb4b4b4(Te6edf3SecureDfuUuidsTb4b4b4.Te6edf3CONTROL_POINTTb4b4b4, Te6edf3responseTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* A [BleConnection] wrapper that uses [AutoRespondingBleService] instead of the plain [FakeBleService], so writes
* to CONTROL_POINT automatically trigger notification responses before the transport's `awaitNotification()`
* suspends.
*/
Tff7b72private Tff7b72class T56d364AutoRespondingBleConnectionTb4b4b4(
Tff7b72private Tff7b72val Te6edf3delegateTb4b4b4: Te6edf3FakeBleConnectionTb4b4b4,
Tff7b72val Te6edf3autoServiceTb4b4b4: Te6edf3AutoRespondingBleServiceTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3BleConnection Tb4b4b4{
Tff7b72override Tff7b72val Te6edf3deviceTb4b4b4: Te6edf3BleDevice?
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3device

Tff7b72override Tff7b72val Te6edf3deviceFlowTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3BleDevice?Tff7b72>
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3deviceFlow

Tff7b72override Tff7b72val Te6edf3connectionStateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3BleConnectionStateTff7b72>
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3connectionState

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectTb4b4b4(Te6edf3deviceTb4b4b4: Te6edf3BleDeviceTb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3connectTb4b4b4(Te6edf3deviceTb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffconnectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4: Te6edf3BleDeviceTb4b4b4, Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4) Tff7b72=
Te6edf3delegateTb4b4b4.Te6edf3connectAndAwaitTb4b4b4(Te6edf3deviceTb4b4b4, Te6edf3timeoutTb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdisconnectTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3delegateTb4b4b4.Te6edf3disconnectTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffprofileTb4b4b4(
Te6edf3serviceUuidTb4b4b4: Te6edf3kotlinTb4b4b4.Te6edf3uuidTb4b4b4.Te6edf3UuidTb4b4b4,
Te6edf3timeoutTb4b4b4: Te6edf3DurationTb4b4b4,
Te6edf3setupTb4b4b4: Te6edf3suspend Te6edf3CoroutineScopeTb4b4b4.Tb4b4b4(Te6edf3BleServiceTb4b4b4) Tff7b72-Tff7b72> Te6edf3TTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3T Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4)Tb4b4b4.Te6edf3setupTb4b4b4(Te6edf3autoServiceTb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffmaximumWriteValueLengthTb4b4b4(Te6edf3writeTypeTb4b4b4: Te6edf3BleWriteTypeTb4b4b4)Tb4b4b4: Tffa657Int? Tff7b72=
Te6edf3delegateTb4b4b4.Te6edf3maximumWriteValueLengthTb4b4b4(Te6edf3writeTypeTb4b4b4)
Tb4b4b4}

T8b949e/**
* Encapsulates the DFU protocol response logic. For each opcode written to CONTROL_POINT, produces the correct
* notification bytes.
*/
Tff7b72private Tff7b72class T56d364DfuResponderTb4b4b4(
Tff7b72private Tff7b72val Te6edf3totalSizeTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72private Tff7b72val Te6edf3totalCrcTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72val Te6edf3selectOffsetTb4b4b4: Tffa657Int Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72private Tff7b72val Te6edf3selectCrcTb4b4b4: Tffa657Int Tff7b72= T79c0ff0Tb4b4b4,
Tff7b72private Tff7b72val Te6edf3maxObjectSizeTb4b4b4: Tffa657Int Tff7b72= Te6edf3DEFAULT_MAX_OBJECT_SIZETb4b4b4,
T8b949e/** The firmware data for computing partial CRCs (needed for CALCULATE_CHECKSUM). */
Tff7b72val Te6edf3firmwareDataTb4b4b4: Te6edf3ByteArray? Tff7b72= Tff7b72nullTb4b4b4,
Tff7b72private Tff7b72val Te6edf3customHandlerTb4b4b4: Tb4b4b4(Tb4b4b4(Tffa657ByteTb4b4b4) Tff7b72-Tff7b72> Te6edf3ByteArray?Tb4b4b4)Tff7b72? Tff7b72= Tff7b72nullTb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72fun Td2a8ffrespondTb4b4b4(Te6edf3opcodeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3accumulatedPacketBytesTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ByteArray? Tb4b4b4{
T8b949e// Check custom handler first
Te6edf3customHandlerTff7b72?.Te6edf3invokeTb4b4b4(Te6edf3opcodeTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Tff7b72return Tffa657it
Tb4b4b4}

Tff7b72return Tff7b72when Tb4b4b4(Te6edf3opcodeTb4b4b4) Tb4b4b4{
Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRN Tff7b72-Tff7b72> Te6edf3buildDfuSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3SET_PRNTb4b4b4)

Te6edf3DfuOpcodeTb4b4b4.Te6edf3SELECT Tff7b72-Tff7b72> Te6edf3buildSelectResponseTb4b4b4(Te6edf3maxObjectSizeTb4b4b4, Te6edf3selectOffsetTb4b4b4, Te6edf3selectCrcTb4b4b4)

Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATE Tff7b72-Tff7b72> Te6edf3buildDfuSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3CREATETb4b4b4)

Te6edf3DfuOpcodeTb4b4b4.Te6edf3CALCULATE_CHECKSUM Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72val Te6edf3crc Tff7b72=
Te6edf3firmwareDataTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3DfuCrc32Tb4b4b4.Te6edf3calculateTb4b4b4(Tffa657itTb4b4b4, Te6edf3length Tff7b72= Te6edf3minOfTb4b4b4(Te6edf3accumulatedPacketBytesTb4b4b4, Tffa657itTb4b4b4.Te6edf3sizeTb4b4b4)Tb4b4b4) Tb4b4b4}
Tff7b72?: Te6edf3totalCrc
Te6edf3buildChecksumResponseTb4b4b4(Te6edf3accumulatedPacketBytesTb4b4b4, Te6edf3crcTb4b4b4)
Tb4b4b4}

Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTE Tff7b72-Tff7b72> Te6edf3buildDfuSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3EXECUTETb4b4b4)

Te6edf3DfuOpcodeTb4b4b4.Te6edf3ABORT Tff7b72-Tff7b72> Te6edf3buildDfuSuccessTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3ABORTTb4b4b4)

Tff7b72else Tff7b72-Tff7b72> Tff7b72null
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Creates a [SecureDfuTransport] already connected to DFU mode with an [AutoRespondingBleService] ready to handle
* DFU commands.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffcreateConnectedTransportTb4b4b4(Te6edf3mtuTb4b4b4: Tffa657Int? Tff7b72= Tff7b72nullTb4b4b4)Tb4b4b4: Te6edf3TestEnv Tb4b4b4{
Tff7b72val Te6edf3scanner Tff7b72= Te6edf3FakeBleScannerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fakeConnection Tff7b72= Te6edf3FakeBleConnectionTb4b4b4(Tb4b4b4)
Te6edf3fakeConnectionTb4b4b4.Te6edf3maxWriteValueLength Tff7b72= Te6edf3mtu
Tff7b72val Te6edf3autoService Tff7b72= Te6edf3AutoRespondingBleServiceTb4b4b4(Te6edf3fakeConnectionTb4b4b4.Te6edf3serviceTb4b4b4)
Tff7b72val Te6edf3autoConnection Tff7b72= Te6edf3AutoRespondingBleConnectionTb4b4b4(Te6edf3fakeConnectionTb4b4b4, Te6edf3autoServiceTb4b4b4)
Tff7b72val Te6edf3factory Tff7b72=
Tff7b72object Tb4b4b4: T56d364BleConnectionFactory Tb4b4b4{
Tff7b72override Tff7b72fun Td2a8ffcreateTb4b4b4(Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4, Te6edf3tagTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3BleConnection Tff7b72= Te6edf3autoConnection
Tb4b4b4}

Tff7b72val Te6edf3transport Tff7b72=
Te6edf3SecureDfuTransportTb4b4b4(
Te6edf3scanner Tff7b72= Te6edf3scannerTb4b4b4,
Te6edf3connectionFactory Tff7b72= Te6edf3factoryTb4b4b4,
Te6edf3address Tff7b72= Te6edf3addressTb4b4b4,
Te6edf3dispatcher Tff7b72= Te6edf3kotlinxTb4b4b4.Te6edf3coroutinesTb4b4b4.Te6edf3DispatchersTb4b4b4.Te6edf3UnconfinedTb4b4b4,
Tb4b4b4)

Te6edf3scannerTb4b4b4.Te6edf3emitDeviceTb4b4b4(Te6edf3FakeBleDeviceTb4b4b4(Te6edf3dfuAddressTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3connectResult Tff7b72= Te6edf3transportTb4b4b4.Te6edf3connectToDfuModeTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3connectResultTb4b4b4.Te6edf3isSuccessTb4b4b4, Ta5d6ff"Ta5d6ffconnectToDfuMode failed: Tffd700${Te6edf3connectResultTb4b4b4.Te6edf3exceptionOrNullTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)

Tff7b72return Te6edf3TestEnvTb4b4b4(Te6edf3transportTb4b4b4, Te6edf3autoServiceTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// DFU response builders
T8b949e// -----------------------------------------------------------------------

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3DEFAULT_MAX_OBJECT_SIZE Tff7b72= T79c0ff4T79c0ff0T79c0ff9T79c0ff6

Tff7b72fun Td2a8ffbuildDfuSuccessTb4b4b4(Te6edf3opcodeTb4b4b4: Tffa657ByteTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72=
Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3RESPONSE_CODETb4b4b4, Te6edf3opcodeTb4b4b4, Te6edf3DfuResultCodeTb4b4b4.Te6edf3SUCCESSTb4b4b4)

Tff7b72fun Td2a8ffbuildDfuFailureTb4b4b4(Te6edf3opcodeTb4b4b4: Tffa657ByteTb4b4b4, Te6edf3resultCodeTb4b4b4: Tffa657ByteTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72=
Te6edf3byteArrayOfTb4b4b4(Te6edf3DfuOpcodeTb4b4b4.Te6edf3RESPONSE_CODETb4b4b4, Te6edf3opcodeTb4b4b4, Te6edf3resultCodeTb4b4b4)

Tff7b72fun Td2a8ffbuildSelectResponseTb4b4b4(Te6edf3maxSizeTb4b4b4: Tffa657IntTb4b4b4, Te6edf3offsetTb4b4b4: Tffa657IntTb4b4b4, Te6edf3crc32Tb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff5Tb4b4b4)
Te6edf3responseTff7b72[T79c0ff0Tff7b72] Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3RESPONSE_CODE
Te6edf3responseTff7b72[T79c0ff1Tff7b72] Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3SELECT
Te6edf3responseTff7b72[T79c0ff2Tff7b72] Tff7b72= Te6edf3DfuResultCodeTb4b4b4.Te6edf3SUCCESS
Te6edf3intToLeBytesTb4b4b4(Te6edf3maxSizeTb4b4b4)Tb4b4b4.Te6edf3copyIntoTb4b4b4(Te6edf3responseTb4b4b4, T79c0ff3Tb4b4b4)
Te6edf3intToLeBytesTb4b4b4(Te6edf3offsetTb4b4b4)Tb4b4b4.Te6edf3copyIntoTb4b4b4(Te6edf3responseTb4b4b4, T79c0ff7Tb4b4b4)
Te6edf3intToLeBytesTb4b4b4(Te6edf3crc32Tb4b4b4)Tb4b4b4.Te6edf3copyIntoTb4b4b4(Te6edf3responseTb4b4b4, T79c0ff1T79c0ff1Tb4b4b4)
Tff7b72return Te6edf3response
Tb4b4b4}

Tff7b72fun Td2a8ffbuildChecksumResponseTb4b4b4(Te6edf3offsetTb4b4b4: Tffa657IntTb4b4b4, Te6edf3crc32Tb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tb4b4b4{
Tff7b72val Te6edf3response Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff1Tb4b4b4)
Te6edf3responseTff7b72[T79c0ff0Tff7b72] Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3RESPONSE_CODE
Te6edf3responseTff7b72[T79c0ff1Tff7b72] Tff7b72= Te6edf3DfuOpcodeTb4b4b4.Te6edf3CALCULATE_CHECKSUM
Te6edf3responseTff7b72[T79c0ff2Tff7b72] Tff7b72= Te6edf3DfuResultCodeTb4b4b4.Te6edf3SUCCESS
Te6edf3intToLeBytesTb4b4b4(Te6edf3offsetTb4b4b4)Tb4b4b4.Te6edf3copyIntoTb4b4b4(Te6edf3responseTb4b4b4, T79c0ff3Tb4b4b4)
Te6edf3intToLeBytesTb4b4b4(Te6edf3crc32Tb4b4b4)Tb4b4b4.Te6edf3copyIntoTb4b4b4(Te6edf3responseTb4b4b4, T79c0ff7Tb4b4b4)
Tff7b72return Te6edf3response
Tb4b4b4}

Tff7b72fun Td2a8ffListTff7b72<Tffa657ByteTff7b72>Tb4b4b4.Te6edf3hexListTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72= Te6edf3map Tb4b4b4{ Ta5d6ff"Ta5d6ff0xTffd700${Tffa657itTb4b4b4.Te6edf3toUByteTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tffd700}Ta5d6ff" Tb4b4b4}Tb4b4b4.Te6edf3toStringTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.17s